#include <stdio.h>
#include <string.h>

#include "mem.h"
#include "object.h"
#include "table.h"
#include "value.h"
#include "vm.h"

#define ALLOCATE_OBJ(type, objectType) (type*) allocateObject(sizeof(type), objectType)

static Obj* allocateObject(size_t size, ObjType type) {
  Obj* obj = (Obj*) reallocate(NULL, 0, size);
  obj->type = type;
  obj->isMarked = false;
  obj->next = vm.objects;

#ifdef DEBUG_LOG_GC
    printf("%p allocate %ld for %d\n", obj, size, type);
#endif
  return obj;
}

static ObjString* allocateString(char* s, int length, uint32_t hash) {
  ObjString* ps = ALLOCATE_OBJ(ObjString, OBJ_STRING);
  ps->length = length;
  ps->str = s;
  ps->hash = hash;
  push(OBJ_VAL(ps));
  putTable(&vm.strings, ps, NULL_VAL);
  pop();

  return ps;
}

static uint32_t hashString(const char* s, int length) {
    uint32_t hash = 2166136261u;
    for (const char* p = s; p - s < length; p++) {
        hash ^= *p;
        hash *= 16777619;
    }
    return hash;
}

ObjString* takeString(char* s, int length) {
    uint32_t hash = hashString(s, length);
    ObjString* interned = tableFindString(&vm.strings, s, length, hash);
    if (interned != NULL) {
        FREE_ARRAY(char, s, length + 1);
        return interned;
    }
    return allocateString(s, length, hash);
}

ObjString* copyString(const char* s, int length) {
  uint32_t hash = hashString(s, length);
  ObjString* interned = tableFindString(&vm.strings, s, length, hash);
  if (interned != NULL) {
      return interned;
  }
  char* p = ALLOCATE(char, length + 1);
  memcpy(p, s, (size_t) length);
  p[length] = 0;

  return allocateString(p, length, hash);
}

ObjFunction* newFunction() {
    ObjFunction* function = ALLOCATE_OBJ(ObjFunction, OBJ_FUNCTION);
    function->arity = 0;
    function->upvalueCount = 0;
    function->name = NULL;
    initChunk(&function->chunk);
    return function;
}

ObjNative* newNative(NativeFunc func) {
    ObjNative* native = ALLOCATE_OBJ(ObjNative, OBJ_NATIVE);
    native->nativeFunc = func;
    return native;
}

ObjClosure *newClosure(ObjFunction *function) {
    int count = function->upvalueCount;
    ObjUpvalue** upvalues = ALLOCATE_OBJ(ObjUpvalue*, count);
    ObjUpvalue** begin = upvalues;
    ObjUpvalue** end = upvalues + count;
    while (*begin++ = NULL, begin < end);

    ObjClosure *closure = ALLOCATE_OBJ(ObjClosure, OBJ_CLOSURE);
    closure->function = function;
    closure->upvalueCount = count;
    closure->upvalues = upvalues;
    return closure;
}

ObjUpvalue* newUpvalue(Value* slot) {
    ObjUpvalue* upvalue = ALLOCATE_OBJ(ObjUpvalue, OBJ_UPVALUE);
    upvalue->closed = NULL_VAL;
    upvalue->location = slot;
    upvalue->next = NULL;
    return upvalue;
}

ObjClass* newClass(ObjString* name) {
    ObjClass* clazz = ALLOCATE_OBJ(ObjClass, OBJ_CLASS);
    clazz->name = name;
    return clazz;
}

ObjInstance* newInstance(ObjClass* clazz) {
    ObjInstance* instance = ALLOCATE_OBJ(ObjInstance, OBJ_INSTANCE);
    instance->clazz = clazz;
    initTable(&instance->fields);
    return instance;
}

static void printFunction(ObjFunction* function) {
    if (function->name == NULL) {
        printf("<script>");
        return;
    }
    printf("<fn %s>", function->name->str);
}

void printObject(Value value) {
    switch (OBJ_TYPE(value)) {
        case OBJ_CLASS:
            printf("%s", AS_CLASS(value)->name->str);
            break;
        case OBJ_CLOSURE:
            printFunction(AS_CLOSURE(value)->function);
            break;
        case OBJ_FUNCTION:
            printFunction(AS_FUNCTION(value));
            break;
        case OBJ_NATIVE:
            printf("<native fn>");
            break;
        case OBJ_STRING:
            printf("%s", AS_CSTRING(value));
            break;
        case OBJ_INSTANCE:
            printf("%s instance", AS_INSTANCE(value)->clazz->name->str);
            break;
        case OBJ_UPVALUE:
            printf("upvalue");
            break;
    }
}
